package net.logax.games.chase.prepare;

import java.util.List;

import net.logax.prepare.ObjSerializable;

public class ObjModelBuilder {

	public static class Prefix {
		public static String VERTEX = "v ";
		public static String NORMAL = "vn ";
		public static String UV_COORDINATE = "vt ";
		public static String FACE = "f ";
	}

	private List<String> lines;

	public ObjModelBuilder(List<String> lines) {
		this.lines = lines;
	}

	public ObjSerializable build() {
		float[] vertices = new float[lines.size() * 3];
		float[] normals = new float[lines.size() * 3];
		float[] uv = new float[lines.size() * 2];

		int vertexIndex = 0;
		int normalIndex = 0;
		int uvIndex = 0;
		int faceIndex = 0;

		int[] facesVerts = new int[lines.size() * 3];
		int[] facesNormals = new int[lines.size() * 3];
		int[] facesUV = new int[lines.size() * 3];
		int numVertices = 0;
		int numNormals = 0;
		int numUV = 0;
		int numFaces = 0;

		for (int i = 0; i < lines.size(); i++) {
			final String line = lines.get(i);

			if (line.startsWith(Prefix.VERTEX)) {
				final String[] tokens = splitLineByWhitespace(line);
				vertices[vertexIndex] = Float.parseFloat(tokens[1]);
				vertices[vertexIndex + 1] = Float.parseFloat(tokens[2]);
				vertices[vertexIndex + 2] = Float.parseFloat(tokens[3]);
				vertexIndex += 3;
				numVertices++;
				continue;
			}

			if (line.startsWith(Prefix.NORMAL)) {
				final String[] tokens = splitLineByWhitespace(line);
				normals[normalIndex] = Float.parseFloat(tokens[1]);
				normals[normalIndex + 1] = Float.parseFloat(tokens[2]);
				normals[normalIndex + 2] = Float.parseFloat(tokens[3]);
				normalIndex += 3;
				numNormals++;
				continue;
			}

			if (line.startsWith(Prefix.UV_COORDINATE)) {
				final String[] tokens = splitLineByWhitespace(line);
				uv[uvIndex] = Float.parseFloat(tokens[1]);
				uv[uvIndex + 1] = Float.parseFloat(tokens[2]);
				uvIndex += 2;
				numUV++;
				continue;
			}

			if (line.startsWith(Prefix.FACE)) {
				final String[] tokens = splitLineByWhitespace(line);
				for (int p = 1; p <= 3; p++) {
					faceIndex = readFacePoint(faceIndex, facesVerts,
							facesNormals, facesUV, numVertices, numNormals,
							numUV, tokens, p);
				}
				numFaces++;
				continue;
			}
		}

		final int maxVetices = numFaces * 3;
		final boolean hasNormals = numNormals > 0;
		int numNormalsFactor = 0;
		if (hasNormals) {
			numNormalsFactor = 3;
		}

		final boolean hasTexCoords = numUV > 0;
		int numUvFactor = 0;
		if (hasTexCoords) {
			numUvFactor = 2;
		}
		float[] verts = new float[(maxVetices)
				* (3 + numNormalsFactor + numUvFactor)];

		for (int j = 0, vi = 0; j < maxVetices; j++) {
			int vertexIdx = facesVerts[j] * 3;
			verts[vi++] = vertices[vertexIdx];
			verts[vi++] = vertices[vertexIdx + 1];
			verts[vi++] = vertices[vertexIdx + 2];

			if (hasTexCoords) {
				int uvIdx = facesUV[j] * 2;
				verts[vi++] = uv[uvIdx];
				verts[vi++] = 1 - uv[uvIdx + 1];
			}

			if (hasNormals) {
				int normalIdx = facesNormals[j] * 3;
				verts[vi++] = normals[normalIdx];
				verts[vi++] = normals[normalIdx + 1];
				verts[vi++] = normals[normalIdx + 2];
			}
		}
		ObjSerializable obj = new ObjSerializable();
		obj.setHasNormals(hasNormals);
		obj.setHasTexCoords(hasTexCoords);
		obj.setMaxVerices(maxVetices);
		obj.setVerts(verts);
		return obj;
	}

	private static String[] splitLineByWhitespace(String line) {
		String[] tokens = line.split("[ ]+");
		return tokens;
	}

	private static int readFacePoint(int faceIndex, int[] facesVerts,
			int[] facesNormals, int[] facesUV, int numVertices, int numNormals,
			int numUV, String[] tokens, final int tokenIndex) {
		final String[] parts = tokens[tokenIndex].split("/");
		facesVerts[faceIndex] = getIndex(parts[0], numVertices);
		final boolean hasNormals = parts.length > 2;
		if (hasNormals) {
			facesNormals[faceIndex] = getIndex(parts[2], numNormals);
		}
		final boolean hasTexCoords = parts.length > 1;
		if (hasTexCoords) {
			facesUV[faceIndex] = getIndex(parts[1], numUV);
		}
		faceIndex++;
		return faceIndex;
	}

	private static int getIndex(String index, int size) {
		final int idx = Integer.parseInt(index);
		if (idx < 0)
			return size + idx;
		else
			return idx - 1;
	}
}
